package repository

import (
	"context"
	"database/sql"
	"time"
	"webook/user/domain"
	"webook/user/repository/cache"
	"webook/user/repository/dao"
)

var ErrUserDuplicate = dao.ErrUserDuplicate
var ErrUserNotFound = dao.ErrDataNotFound

//go:generate mockgen -source=./user.go -package=repomocks -destination=mocks/user.mock.go UserRepository
type UserRepository interface {
	Create(ctx context.Context, u domain.User) error
	// Update 更新数据，只有非 0 值才会更新
	Update(ctx context.Context, u domain.User) error
	FindByPhone(ctx context.Context, phone string) (domain.User, error)
	FindByEmail(ctx context.Context, email string) (domain.User, error)
	FindById(ctx context.Context, id int64) (domain.User, error)
}

type userRepository struct {
	dao   dao.UserDAO
	cache cache.UserCache
}

func NewUserRepository(d dao.UserDAO, c cache.UserCache) UserRepository {
	return &userRepository{
		dao:   d,
		cache: c,
	}
}

func (repo *userRepository) Create(ctx context.Context, u domain.User) error {
	return repo.dao.Insert(ctx, dao.User{
		Email: sql.NullString{
			String: u.Email,
			Valid:  u.Email != "",
		},
		Phone: sql.NullString{
			String: u.Phone,
			Valid:  u.Phone != "",
		},
		Password: u.Password,
	})
}

func (repo *userRepository) Update(ctx context.Context, u domain.User) error {
	err := repo.dao.UpdateNonZeroFields(ctx, domainToEntity(u))
	if err != nil {
		return err
	}
	return repo.cache.Delete(ctx, u.Id)
}

func (repo *userRepository) FindByPhone(ctx context.Context, phone string) (domain.User, error) {
	user, err := repo.dao.FindByPhone(ctx, phone)
	return entityToDomain(user), err
}

func (repo *userRepository) FindByEmail(ctx context.Context, email string) (domain.User, error) {
	user, err := repo.dao.FindByEmail(ctx, email)
	return entityToDomain(user), err
}

func (repo *userRepository) FindById(ctx context.Context, id int64) (domain.User, error) {
	user, err := repo.cache.Get(ctx, id)
	if err == nil {
		return user, nil
	}
	user2, err := repo.dao.FindById(ctx, id)
	if err != nil {
		return domain.User{}, err
	}
	_ = repo.cache.Set(ctx, entityToDomain(user2))
	return entityToDomain(user2), err
}

func domainToEntity(u domain.User) dao.User {
	return dao.User{
		Id: u.Id,
		Email: sql.NullString{
			String: u.Email,
			Valid:  u.Email != "",
		},
		Phone: sql.NullString{
			String: u.Phone,
			Valid:  u.Phone != "",
		},
		Nickname: sql.NullString{
			String: u.Nickname,
			Valid:  u.Nickname != "",
		},
		Birthday: sql.NullInt64{
			Int64: u.Birthday.UnixMilli(),
			Valid: u.Birthday.IsZero(),
		},
		AboutMe: sql.NullString{
			String: u.AboutMe,
			Valid:  u.AboutMe != "",
		},
		Password: u.Password,
	}
}

func entityToDomain(user dao.User) domain.User {
	var birthday time.Time
	if user.Birthday.Valid {
		birthday = time.UnixMilli(user.Birthday.Int64)
	}
	return domain.User{
		Id:       user.Id,
		Email:    user.Email.String,
		Phone:    user.Phone.String,
		Password: user.Password,
		Nickname: user.Nickname.String,
		AboutMe:  user.AboutMe.String,
		Birthday: birthday,
		Ctime:    time.UnixMilli(user.Ctime),
	}
}
